package cn.sciento.starter.social.core.security;

import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;
import cn.sciento.starter.social.core.provider.SocialProviderRepository;
import cn.sciento.starter.social.core.provider.SocialUserProviderRepository;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.social.security.SocialAuthenticationFailureHandler;
import org.springframework.social.security.SocialAuthenticationServiceLocator;
import org.springframework.util.Assert;

public class SpringSocialConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
  private static final String DEFAULT_FILTER_PROCESSES_URL = "/open";
  
  private String postLoginUrl;
  
  private String postFailureUrl;
  
  private String bindUrl;
  
  private boolean attemptBind;
  
  private boolean updateBind = true;
  
  private String defaultFailureUrl;
  
  private String filterProcessesUrl = "/open";
  
  private boolean alwaysUsePostLoginUrl = false;
  
  private boolean enableHttps = false;
  
  private AuthenticationSuccessHandler successHandler;
  
  private AuthenticationFailureHandler failureHandler;
  
  private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource;
  
  public void configure(HttpSecurity http) throws Exception {
    ApplicationContext applicationContext = (ApplicationContext)http.getSharedObject(ApplicationContext.class);
    SocialAuthenticationServiceLocator authServiceLocator = getDependency(applicationContext, SocialAuthenticationServiceLocator.class);
    SocialProviderRepository socialProviderRepository = getDependency(applicationContext, SocialProviderRepository.class);
    SocialUserProviderRepository socialUserProviderRepository = getDependency(applicationContext, SocialUserProviderRepository.class);
    SocialAuthenticationProvider socialAuthenticationProvider = getDependency(applicationContext, SocialAuthenticationProvider.class);
    TokenStore tokenStore = getDependency(applicationContext, TokenStore.class);
    SocialAuthenticationFilter filter = new SocialAuthenticationFilter((AuthenticationManager)http.getSharedObject(AuthenticationManager.class), tokenStore, socialUserProviderRepository, authServiceLocator, socialProviderRepository);
    RememberMeServices rememberMe = (RememberMeServices)http.getSharedObject(RememberMeServices.class);
    if (rememberMe != null)
      filter.setRememberMeServices(rememberMe); 
    if (this.postLoginUrl != null) {
      filter.setPostLoginUrl(this.postLoginUrl);
      filter.setAlwaysUsePostLoginUrl(this.alwaysUsePostLoginUrl);
    } 
    if (this.postFailureUrl != null)
      filter.setPostFailureUrl(this.postFailureUrl); 
    if (this.bindUrl != null)
      filter.setBindUrl(this.bindUrl); 
    filter.setEnableHttps(this.enableHttps);
    filter.setAttemptBind(this.attemptBind);
    filter.setUpdateBind(this.updateBind);
    if (this.defaultFailureUrl != null)
      filter.setDefaultFailureUrl(this.defaultFailureUrl); 
    http
      .authenticationProvider(socialAuthenticationProvider)
      .addFilterBefore((Filter)postProcess(filter), AbstractPreAuthenticatedProcessingFilter.class);
  }
  
  protected <T> T postProcess(T object) {
    SocialAuthenticationFilter filter = (SocialAuthenticationFilter)super.postProcess(object);
    filter.setFilterProcessesUrl(this.filterProcessesUrl);
    if (this.successHandler != null)
      filter.setAuthenticationSuccessHandler(this.successHandler); 
    if (this.failureHandler != null)
      filter.setAuthenticationFailureHandler((AuthenticationFailureHandler)new SocialAuthenticationFailureHandler(this.failureHandler)); 
    if (this.authenticationDetailsSource != null)
      filter.setAuthenticationDetailsSource(this.authenticationDetailsSource); 
    return (T)filter;
  }
  
  private <T> T getDependency(ApplicationContext applicationContext, Class<T> dependencyType) {
    try {
      T dependency = (T)applicationContext.getBean(dependencyType);
      return dependency;
    } catch (NoSuchBeanDefinitionException e) {
      throw new IllegalStateException("SpringSocialConfigurer depends on " + dependencyType.getName() + ". No single bean of that type found in application context.", e);
    } 
  }
  
  public SpringSocialConfigurer postLoginUrl(String postLoginUrl) {
    this.postLoginUrl = postLoginUrl;
    return this;
  }
  
  public SpringSocialConfigurer alwaysUsePostLoginUrl(boolean alwaysUsePostLoginUrl) {
    this.alwaysUsePostLoginUrl = alwaysUsePostLoginUrl;
    return this;
  }
  
  public SpringSocialConfigurer postFailureUrl(String postFailureUrl) {
    this.postFailureUrl = postFailureUrl;
    return this;
  }
  
  public SpringSocialConfigurer bindUrl(String bindUrl) {
    this.bindUrl = bindUrl;
    return this;
  }
  
  public SpringSocialConfigurer setAttemptBind(boolean attemptBind) {
    this.attemptBind = attemptBind;
    return this;
  }
  
  public SpringSocialConfigurer setUpdateBind(boolean updateBind) {
    this.updateBind = updateBind;
    return this;
  }
  
  public SpringSocialConfigurer setEnableHttps(boolean enableHttps) {
    this.enableHttps = enableHttps;
    return this;
  }
  
  public SpringSocialConfigurer defaultFailureUrl(String defaultFailureUrl) {
    this.defaultFailureUrl = defaultFailureUrl;
    return this;
  }
  
  public SpringSocialConfigurer successHandler(SocialSuccessHandler successHandler) {
    this.successHandler = (AuthenticationSuccessHandler)successHandler;
    return this;
  }
  
  public SpringSocialConfigurer failureHandler(SocialFailureHandler failureHandler) {
    this.failureHandler = failureHandler;
    return this;
  }
  
  public SpringSocialConfigurer authenticationDetailsSource(AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
    this.authenticationDetailsSource = authenticationDetailsSource;
    return this;
  }
  
  public SpringSocialConfigurer setFilterProcessesUrl(String filterProcessesUrl) {
    Assert.notNull(filterProcessesUrl, "filter process url should not be null.");
    this.filterProcessesUrl = filterProcessesUrl;
    return this;
  }
}
